#include <sys/types.h>
#include <sys/stat.h> 
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <limit.h>
#include <Singleton.h>
#include <MarkupSTL.h>
#include <memcheck.h>
#include <sstream>
#include <log.h>
#include <http_decoder.h>

HTTP_SVR_NS_BEGIN

CLimitPolicy::CLimitPolicy():
_confHeader (NULL)
{

}

CLimitPolicy::~CLimitPolicy()
{
    FREE_CLEAR(_confHeader);

    _blankList.clear();
    _proxyList.clear();
}

CLimitPolicy* CLimitPolicy::Instance(void)
{
    return CSingleton<CLimitPolicy>::Instance();
}

void CLimitPolicy::Destroy(void)
{
    return CSingleton<CLimitPolicy>::Destroy(); 
}

int CLimitPolicy::config_init(const char* xmlfile)
{
    CMarkupSTL  markup;
    if(!markup.Load(xmlfile))
    {       
        log_boot("Load xml [%s] failed", xmlfile);
        return -1;
    }

    if(!markup.FindElem("SYSTEM"))
    {
        log_boot("Can not FindElem [SYSTEM] in xml [%s] failed", xmlfile);
        return -1;
    }

    uint16_t uCycle    = atoi(markup.GetAttrib("AccessCycle").c_str());
    uint16_t uAccess   = atoi(markup.GetAttrib("AccessLimit").c_str());
    uint32_t uCapacity = atoi(markup.GetAttrib("LimitCapacity").c_str());

    if(uCapacity < MAX_BUCKET_LEN)
    {
        uCapacity = MAX_BUCKET_LEN;
        log_boot("Limit capacity less than [%d], reset it to [%d]", MAX_BUCKET_LEN, MAX_BUCKET_LEN);
    }
    else
    {
        uCapacity += uCapacity % MAX_BUCKET_LEN;
    }
    
    if(0 == (uCapacity / MAX_BUCKET_LEN) % 4)
        uCapacity += 3 * MAX_BUCKET_LEN;

    markup.IntoElem();

    if(markup.FindElem("PROXY"))
    {
        string szProxy = markup.GetAttrib("Addr");
        
        stringstream ssProxy(szProxy);
        while(true)
        {
            string szToken;   
            getline(ssProxy, szToken, ',');   
            if(ssProxy.fail()) 
                break;

            _proxyList.push_back(szToken);;
        }
    }

    if(!markup.FindElem("WHITELIST"))
    {
        log_boot("Can not FindElem [WHITELIST] in xml [%s] failed", xmlfile);
        return -1;
    }

    markup.IntoElem();

    while(markup.FindElem("IP"))
    {
        string addr = markup.GetAttrib("Addr");
        if(0 == addr.length())
            continue;

        _blankList.push_back(addr);
    }

    int totalSize = sizeof(TConfLimit) + uCapacity * sizeof(TItemLimit);

    _confHeader = (TConfLimit*)shm_init("limit.dat", totalSize);
    if(NULL == _confHeader)
    {
        log_boot("Init shared memory for limit data failed");
        return -1;
    }

    memset(_confHeader, 0x00, totalSize);

    _confHeader->_cfgCount = uAccess;
    _confHeader->_cfgCycle = uCycle;
    _confHeader->_limitCap = uCapacity;
    
    return 0;
}

void* CLimitPolicy::shm_init(const char* filename, int size)
{    
    int fd = ::open(filename, O_RDWR|O_CREAT, 0666);
    void *map = NULL;

    if(fd >= 0)
    {
        if(size > 0){
            if(ftruncate(fd, size)){};
		}
        else
            size = lseek(fd, 0L, SEEK_END);

        if(size > 0)
            map = mmap(0, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
        ::close(fd);
    } else if(size > 0) {
        map = mmap(0, size, PROT_READ|PROT_WRITE, MAP_ANONYMOUS, -1, 0);
    }

    if(map == MAP_FAILED)
        map = NULL;

    return map;
}

int CLimitPolicy::check_limit_by_qvia(const char* buffer)
{
    unsigned long int ip;
    if(CHttpDecoder::Instance()->get_real_ip(buffer, ip) == 0)
    {
        return check_limit(ip);
    }

    return 0;
}

int CLimitPolicy::check_limit(unsigned long int ip)
{
    struct in_addr in;
    in.s_addr = ip;

    vector< string >::iterator iter = _blankList.begin();
    for(; iter < _blankList.end(); iter++)
    {
        if(CHttpDecoder::Instance()->my_str_comp((*iter).c_str(), inet_ntoa(in)))
        {
            log_debug("the item[%s] is in the blank list, let it pass", ip_str(ip));
            return 0;
        }
    }

    TItemLimit* pLimitData = NULL;
    if(0 != record_ip(ip, pLimitData))
    {
        log_error("call function record ip failed, let it pass, ip [%s]", ip_str(ip));

        return 0;
    }

    if(!pLimitData->_bLocked)
    {
        log_debug("the item [%s] is unlocked, let it pass", ip_str(ip));
                
        return 0;
    }

    return -1;
}

int CLimitPolicy::record_ip(unsigned long int ip, TItemLimit*& pLimitData)
{
    int iPos = 0, i = 0;
    TItemLimit*  pstLimit    = NULL;
    TItemLimit*  pstFree     = NULL;
    TItemLimit*  pstFew      = NULL;
    TItemLimit*  pstThis     = NULL;
    time_t       tTime;

    time(&tTime);

    iPos = hash_string(ip_str(ip), MAX_IPSTR_LEN) % (_confHeader->_limitCap / MAX_BUCKET_LEN);

    for(i = 0; i < MAX_BUCKET_LEN; i++)
    {
        pstLimit = (TItemLimit *)(_confHeader->_memData + ((iPos * MAX_BUCKET_LEN  + i) * sizeof(TItemLimit)));

        if(NULL == pstFree && IsFree(pstLimit, tTime))
        {
            pstFree = pstLimit;
        }

        if(NULL == pstFew) 
        {
            pstFew = pstLimit;
        }
        else 
        {
            if(pstFew->_curAccess > pstLimit->_curAccess)
            {
                pstFew = pstLimit;
            }
        }

        if(pstLimit->_lockIp == ip)
        {
            pstThis = pstLimit;

            break;
        }
    }

    if (NULL != pstThis)
    {
        pstThis->_curAccess ++;

        if(pstThis->_bLocked)
        {
            if((tTime - pstThis->_startTime) >= _confHeader->_cfgCycle)
            {
                log_debug("Item [%s] enter the new cycle, recalculate the lock time", ip_str(ip));

                pstThis->_curAccess = 1;
                pstThis->_startTime = tTime;
                pstThis->_bLocked = false;
            }
        }
        else
        {
            if(pstThis->_curAccess > _confHeader->_cfgCount)
            {
                log_error("**Client IP[%s] reach the max access times, locked**", ip_str(ip));

                pstThis->_bLocked = true;
            }
        }
        
        pLimitData = pstThis;

        return 0;
    }

    if (NULL != pstFree)
    {
        log_debug("Replace the free item, new ip[%s]", ip_str(ip));

        pstFree->_lockIp = ip;
        pstFree->_curAccess = 1;
        pstFree->_startTime = tTime;
        pstFree->_bLocked = false;
        
        pLimitData = pstFree;

        return 0;
    }

    if (NULL != pstFew)
    {
        log_debug("Replace the fewest item, old ip[%s], new ip[%s]", ip_str(pstFew->_lockIp), ip_str(ip));
                
        pstFew->_lockIp = ip;
        pstFew->_curAccess = 1;
        pstFew->_startTime = tTime;
        pstFew->_bLocked = false;

        pLimitData = pstFew;

        return 0;
    }

    return -1;
}

uint32_t CLimitPolicy::hash_string(char* arKey, uint32_t nKeyLength)
{ 
    uint32_t h       = 0;
    uint32_t g       = 0;
    char*  arEnd     = arKey + nKeyLength;

    while (arKey < arEnd)
    { 
        h = (h << 4) + *arKey++;

        if ((g = (h & 0xF0000000)))
        {
            h = h ^ (g >> 24);
            h = h ^ g;
        }
    }

    return h;
}

bool CLimitPolicy::IsFree(TItemLimit* pLimitData, time_t tTime)
{
    if((tTime - pLimitData->_startTime) > _confHeader->_cfgCycle)
    {
        return true;
    }

    return false;
}

char* CLimitPolicy::ip_str(unsigned long int ip)
{
    struct in_addr in;
    in.s_addr = ip;

    return inet_ntoa(in);
}

int CLimitPolicy::is_from_proxy(unsigned long int ip)
{
    struct in_addr in;
    in.s_addr = ip;
    
    vector<string>::iterator iter = _proxyList.begin();
    for(; iter < _proxyList.end(); iter++)
    {
       if(CHttpDecoder::Instance()->my_str_comp((*iter).c_str(), inet_ntoa(in)))
       {
            return 0; 
       }
    }

    return -1;
}

HTTP_SVR_NS_END
